package main

import (
	"basic-go/src/arrays"
	"fmt"
)

/*

切片（slice）是对数组的一个连续片段的引用，所以切片是一个引用类型，这个片段可以是整个数组，也可以是由起始和终止索引标识的一些项的子集，需要注意的是，终止索引标识的项不包括在切片内。

Go语言中切片的内部结构包含地址、大小和容量，切片一般用于快速地操作一块数据集合，如果将数据集合比作切糕的话，切片就是你要的“那一块”，切的过程包含从哪里开始（切片的起始位置）及切多大（切片的大小），容量可以理解为装切片的口袋大小。

切片默认指向一段连续内存区域，可以是数组，也可以是切片本身。

从连续内存区域生成切片是常见的操作，格式如下：

slice[开始位置 : 结束位置]

slice - 表示目标切片对象
开始位置 - 对应目标切片的开始索引
结束位置 - 对应目标切片的结束索引

从数组或切片生成新的切片拥有如下特性：
1. 取出的元素数量为：结束位置 - 开始位置
2. 取出元素不包含结束位置对应的索引，切片最后一个元素使用slice[len(slice)]获取
3. 当缺省开始位置时，表示从连续区域开头到结束位置
4. 当缺省结束位置时，表示从开始位置到整个连续区域末尾
5. 两者同时缺省时，与切片本身等效
6. 两者同时为0时，等效于空切片，一般用于切片复位

*/

func main() {

	var a = [3]int{1, 2, 3}
	var slice1 = a[0:2]
	fmt.Println(a, slice1, len(slice1))
	fmt.Printf("%T %T\n", a, slice1)

	slice1 = a[0:0]
	fmt.Println(slice1)
	fmt.Printf("%T\n", slice1)

	slice1 = a[:len(a)]
	fmt.Println(slice1)
	fmt.Printf("%T\n", slice1)

	slice1 = a[0:]
	fmt.Println(slice1)
	fmt.Printf("%T\n", slice1)

	// 综合使用
	var arr [30]int
	for i := 1; i <= len(arr); i++ {
		arr[i-1] = i
	}
	fmt.Println(arr)
	fmt.Println(arr[10:17])
	fmt.Println(arr[:17])
	fmt.Println(arr[10:])
	fmt.Println(arr[:])
	// 重置切片，清空拥有的元素
	fmt.Println(arr[0:0])

	// 直接声明新的切片
	var strList []string
	// 声明整型切片
	var numList []int
	// 声明一个空切片
	var numListEmpty = []int{}
	// 输出3个切片
	fmt.Println(strList, numList, numListEmpty)
	// 输出3个切片大小
	fmt.Println(len(strList), len(numList), len(numListEmpty))
	// 切片判定空的结果
	fmt.Println(strList == nil)
	fmt.Println(numList == nil)
	fmt.Println(numListEmpty == nil)

	// 使用make()函数构造切片
	a2 := make([]int, 2)
	b2 := make([]int, 2, 10)
	fmt.Println(a2, b2)
	fmt.Println(len(a2), len(b2))

	// 切片追加
	var a3 []int
	a3 = append(a3, 1)
	fmt.Println(a3)
	a3 = append(a3, 1, 2, 3)
	fmt.Println(a3)
	a3 = append(a3, []int{1, 2, 3}...)
	fmt.Println(a3)

	// 在开头添加
	// 在切片开头添加元素一般都会导致内存的重新分配，而且会导致已有元素全部被复制1次，因此从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。
	var a4 = []int{1, 2, 3}
	a4 = append([]int{0}, a4...)
	fmt.Println(a4)
	a4 = append([]int{-3, -2, -1}, a4...)
	fmt.Println(a4)

	// 链式操作
	// var a []int
	// a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
	// a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

	// 从切片中删除元素
	var a5 = []int{1, 2, 3}
	a5 = a5[1:] // 删除开头1个元素
	fmt.Println(a5)
	a5 = a5[2:] // 删除开头N个元素
	fmt.Println(a5)

	// 也可以不移动数据指针，但是将后面的数据向开头移动，可以用append原地完成（所谓原地完成是指在原有的切片数据对应的内存区间内完成，不会导致内存空间结构的变化）：
	a5 = []int{1, 2, 3}
	a5 = append(a5[:0], a5[1:]...) // 删除开头1个元素
	fmt.Println(a5)
	a5 = append(a5[:0], a5[2:]...) // 删除开头N个元素
	fmt.Println(a5)

	// 从中间位置删除
	// a5 = []int{1, 2, 3, 4, 5, 6, 7, 8}
	// a5 = append(a5[:i], a5[i+1:]...) // 删除中间1个元素
	// a5 = append(a5[:i], a5[i+N:]...) // 删除中间N个元素

	// 从尾部删除
	// a5 = []int{1, 2, 3, 4, 5, 6, 7, 8}
	// a5 = a5[:len(a5)-1] // 删除尾部1个元素
	// a5 = a5[:len(a5)-N] // 删除尾部N个元素

	a5 = []int{1, 2, 3, 4, 5, 6, 7, 8}
	a5 = remove(a5, 4)
	fmt.Println("a5 = ", a5)
	a5 = insert(a5, 5, 0)
	fmt.Println("a5 = ", a5)

	// 可能验证出了操作的是同一块内存区域
	a5 = []int{1, 2, 3, 4, 5, 6, 7, 8}
	var r = remove(a5, 3)
	fmt.Println(r)
	fmt.Println(a5)

	a5 = []int{1, 2, 3, 4, 5, 6, 7, 8}
	var r2 = insert(a5, 5, 0)
	fmt.Println(r2)
	fmt.Println(a5)
}

func remove(slice []int, index int) []int {
	return arrays.Remove(slice, index)
}

func insert(slice []int, index int, value int) []int {
	return arrays.Insert(slice, index, value)
}
